The aim of these exercises is to improve your ability to deal with multi-predictor linear models where the predictors are all continuous or a mixture of continuous and categorical.

A

Let’s continue with the example from Chapter 7 assessing whether dietary supplements improve the perceived health of dogs with osteoarthritis. The model we focused on at the end of that exercise was one modelling the pain index of dogs after 60 days as a function of whether they received dietary supplements or a placebo and the sex of the dog. The dogs unavoidably varied in body weight, ranging from 14-47 kg. To partly account for this, the authors adjusted the doses to a constant amount per kg of body weight. However, you can probably think of a range of ways in which weight might affect osteoarthritis. The model using treatment and sex fitted the data fairly well, with r2 around 0.6. We detected a strong treatment effect, but it is possible that if we reduced background noise, we might see sex-specific responses and we’d also get a more precise estimate of the effects.

Think about the steps you’d take to see if it would be helpful to include body weight in the model, then go back to the data and run the analysis.

Start by reloading the data file from Chapter 7 exercises

df <- read.csv("data/martello.csv")
head(df,10)
#Tidy up the names if original data used
#df_names<-c(GROUP="group", sterilizzato = "ster", BCS = "bcs", PESO.KG = "wt", ETA = "eta", RAZZA = "breed",
#                  HCPI = "hcp0", HCPI.4 = "hcp40", HCP.6 = "hcp60",
#                  SEGNI.OA.VET = "vet0", SEGNI.AO.VET.4 = "vet40", SEGNI.AO.VET.6 = "vet60")
#df<-rename(df, all_of(df_names))
# make sex, treatment, and sterilization factors
df$sex<-as.factor(df$sex)
df$group<-as.factor(df$group)
df$ster<-as.factor(df$ster)
#set contrasts to sum
contrasts=list(group=contr.sum, sex=contr.sum, ster=contr.sum)

The model we had in that exercise linked final owner-assessed health (hcp60) to treatment (group), sex, and possibly sterilization status (ster).

For simplicity, let’s use the treatment/sex model, and start by running it for comparison

martello.lm <- lm(hcp60~group*sex, data=df)
summary(martello.lm)

Call:
lm(formula = hcp60 ~ group * sex, data = df)

Residuals:
    Min      1Q  Median      3Q     Max 
-10.636  -2.667  -0.455   3.167   9.364 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept)      30.64       1.44   21.29  < 2e-16 ***
groupTRT         -9.97       2.15   -4.65  4.4e-05 ***
sexM             -3.75       2.15   -1.75    0.089 .  
groupTRT:sexM     1.54       3.03    0.51    0.616    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 4.77 on 36 degrees of freedom
Multiple R-squared:  0.548, Adjusted R-squared:  0.511 
F-statistic: 14.6 on 3 and 36 DF,  p-value: 2.25e-06
emmeans(martello.lm, ~ group:sex)
 group sex emmean   SE df lower.CL upper.CL
 CTR   F     30.6 1.44 36     27.7     33.6
 TRT   F     20.7 1.59 36     17.4     23.9
 CTR   M     26.9 1.59 36     23.7     30.1
 TRT   M     18.4 1.44 36     15.5     21.4

Confidence level used: 0.95 

Start by checking covariate ranges

boxplot(wt~group*sex, data=df)

Some slight differences, including sex-based ones, but ranges overlap, so probably OK

Now run model and look at residuals. Try model with 3 slopes terms first

martello2.lm <- lm(hcp60~group*sex*wt, data=df)
plot(martello2.lm)

summary(martello2.lm)

Call:
lm(formula = hcp60 ~ group * sex * wt, data = df)

Residuals:
   Min     1Q Median     3Q    Max 
-9.753 -3.056  0.016  3.248  9.118 

Coefficients:
                 Estimate Std. Error t value Pr(>|t|)    
(Intercept)        40.631      8.520    4.77  3.9e-05 ***
groupTRT          -27.277     11.872   -2.30    0.028 *  
sexM              -13.625     11.140   -1.22    0.230    
wt                 -0.388      0.326   -1.19    0.242    
groupTRT:sexM      17.129     14.530    1.18    0.247    
groupTRT:wt         0.642      0.430    1.49    0.146    
sexM:wt             0.385      0.382    1.01    0.321    
groupTRT:sexM:wt   -0.584      0.494   -1.18    0.246    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 4.88 on 32 degrees of freedom
Multiple R-squared:  0.58,  Adjusted R-squared:  0.488 
F-statistic: 6.31 on 7 and 32 DF,  p-value: 0.000107
Anova(martello2.lm, type='III')
Anova Table (Type III tests)

Response: hcp60
             Sum Sq Df F value  Pr(>F)    
(Intercept)     542  1   22.74 3.9e-05 ***
group           126  1    5.28   0.028 *  
sex              36  1    1.50   0.230    
wt               34  1    1.42   0.242    
group:sex        33  1    1.39   0.247    
group:wt         53  1    2.22   0.146    
sex:wt           24  1    1.01   0.321    
group:sex:wt     33  1    1.39   0.246    
Residuals       763 32                    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Nothing here to suggest that slopes are differnt across the categorical predictors, so lets run an intercepts model

martello3.lm <- lm(hcp60~group*sex+wt, data=df)
plot(martello3.lm)

summary(martello3.lm)

Call:
lm(formula = hcp60 ~ group * sex + wt, data = df)

Residuals:
    Min      1Q  Median      3Q     Max 
-10.689  -2.743  -0.366   3.019   9.544 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept)    30.0358     2.9452   10.20  5.1e-12 ***
groupTRT      -10.0435     2.1967   -4.57  5.8e-05 ***
sexM           -3.9639     2.3616   -1.68     0.10    
wt              0.0233     0.0995    0.23     0.82    
groupTRT:sexM   1.7428     3.1992    0.54     0.59    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 4.84 on 35 degrees of freedom
Multiple R-squared:  0.549, Adjusted R-squared:  0.498 
F-statistic: 10.7 on 4 and 35 DF,  p-value: 9.35e-06
Anova(martello3.lm, type='III')
Anova Table (Type III tests)

Response: hcp60
            Sum Sq Df F value  Pr(>F)    
(Intercept)   2433  1  104.00 5.1e-12 ***
group          489  1   20.90 5.8e-05 ***
sex             66  1    2.82    0.10    
wt               1  1    0.06    0.82    
group:sex        7  1    0.30    0.59    
Residuals      819 35                    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
emmeans(martello3.lm, ~group|sex)
sex = F:
 group emmean   SE df lower.CL upper.CL
 CTR     30.7 1.51 35     27.7     33.8
 TRT     20.7 1.61 35     17.4     24.0

sex = M:
 group emmean   SE df lower.CL upper.CL
 CTR     26.8 1.70 35     23.3     30.2
 TRT     18.5 1.46 35     15.5     21.4

Confidence level used: 0.95 

Body weight plays very little role here. Adding it to the model only raises the variance explained by 0.1%, and not surprisingly, the estimated effects haven’t changed much either.

B

LaRosa and Connor (Amer. J. Bot., 2017) examined effects of several floral traits on fitness components of milkweeds, Asclepias spp. The fitness components were male and female pollination success and female reproductive success.

In the paper, their analysis focused on 6 predictors, They measured six floral traits, although one of them, hood height, was not relevant for Asclepias viridiflora, which was the species with the largest sample size. - gynostegium width, - hood length, - hood height, - horn reach, - slit length, and - gap width

Their Figure 2 shows what these measurements represent on flowers.

The data are available from Dryad here.

The floral traits were different between species, so data analysis would require each species to be analyzed separately or for the measurements to be standardized within each species.

df <- read.csv("data/larosa.csv")
head(df,10)
df_syr<-subset(df,species=="Asyr")
df_vir<-subset(df, species=='Avir')

Fitness component estimates were relativized by dividing by the mean, and the traits were standardized to a mean of zero and standard deviation of one.

Start by looking at A. syriaca, then for comparison, look at how these floral traits affect A. viridiflora

First look at the removals per flower

What checks should you do before assessing the predictors’ effects?

scatterplotMatrix(data=df_syr,~ removals.per.flower + gyn.width + hood.length + hood.height + horn.reach + slit.length + gap.width)

cor(df_syr[,c('gyn.width', 'hood.length', 'hood.height', 'horn.reach', 'slit.length', 'gap.width')])
            gyn.width hood.length hood.height horn.reach slit.length gap.width
gyn.width      1.0000       0.299       0.280      0.228     0.38647   0.04232
hood.length    0.2985       1.000       0.418      0.626     0.26245   0.15720
hood.height    0.2800       0.418       1.000      0.610     0.33986   0.25232
horn.reach     0.2282       0.626       0.610      1.000     0.34591   0.25328
slit.length    0.3865       0.262       0.340      0.346     1.00000   0.00769
gap.width      0.0423       0.157       0.252      0.253     0.00769   1.00000
vif(lm(data=df_syr, removals.per.flower ~ gyn.width + hood.length + hood.height + horn.reach + slit.length + gap.width))
  gyn.width hood.length hood.height  horn.reach slit.length   gap.width 
       1.26        1.72        1.71        2.26        1.31        1.10 

Diagnostics OK

syr1.lm<-lm(data=df_syr, removals.per.flower ~ gyn.width + hood.length + hood.height + horn.reach + slit.length + gap.width)
plot(syr1.lm)

summary(syr1.lm)

Call:
lm(formula = removals.per.flower ~ gyn.width + hood.length + 
    hood.height + horn.reach + slit.length + gap.width, data = df_syr)

Residuals:
    Min      1Q  Median      3Q     Max 
-2.0796 -0.4980  0.0352  0.7252  2.1521 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)   
(Intercept)     2.01       3.54    0.57   0.5730   
gyn.width     -18.93      15.07   -1.26   0.2166   
hood.length    12.90       4.96    2.60   0.0131 * 
hood.height    13.83       4.76    2.90   0.0061 **
horn.reach    -10.92       7.09   -1.54   0.1315   
slit.length   -16.48      15.67   -1.05   0.2995   
gap.width     -28.02      14.19   -1.97   0.0556 . 
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 1.06 on 38 degrees of freedom
Multiple R-squared:  0.339, Adjusted R-squared:  0.234 
F-statistic: 3.24 on 6 and 38 DF,  p-value: 0.0113

If you’re happy with your pre-flight checks, fit the model and make some conclusions about the effects of each predictor, including any notes of caution

Look at results of model fitting

options(digits=3)
tidy(syr1.lm, conf.int = TRUE)
glance(syr1.lm)

Get standardized coefficients with lmbeta

lm.beta.syr1 <- lm.beta(syr1.lm)
tidy(lm.beta.syr1, conf.int = TRUE)

Run through same sequence for the other two life-history traits

First do insertions.per.flower

scatterplotMatrix(data=df_syr,~ insertions.per.flower + gyn.width + hood.length + hood.height + horn.reach + slit.length + gap.width)


syr2.lm<-lm(data=df_syr, insertions.per.flower ~ gyn.width + hood.length + hood.height + horn.reach + slit.length + gap.width)
plot(syr2.lm)

summary(syr2.lm)

Call:
lm(formula = insertions.per.flower ~ gyn.width + hood.length + 
    hood.height + horn.reach + slit.length + gap.width, data = df_syr)

Residuals:
   Min     1Q Median     3Q    Max 
-0.417 -0.173 -0.009  0.122  0.571 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)
(Intercept)   0.0181     0.7873    0.02     0.98
gyn.width    -1.1430     3.3512   -0.34     0.73
hood.length   1.7626     1.1026    1.60     0.12
hood.height   1.7360     1.0592    1.64     0.11
horn.reach   -2.5932     1.5759   -1.65     0.11
slit.length  -0.4547     3.4844   -0.13     0.90
gap.width    -3.0716     3.1561   -0.97     0.34

Residual standard error: 0.235 on 38 degrees of freedom
Multiple R-squared:  0.138, Adjusted R-squared:  0.00134 
F-statistic: 1.01 on 6 and 38 DF,  p-value: 0.433
options(digits=3)
tidy(syr2.lm, conf.int = TRUE)
glance(syr2.lm)

lm.beta.syr2 <- lm.beta(syr2.lm)
tidy(lm.beta.syr2, conf.int = TRUE)

Model explains less of variation; overall rsq much lower. Same two predictors have highest standardized coefficients, but largely noise here

Now do numbers of fruits

scatterplotMatrix(data=df_syr,~ fruits + gyn.width + hood.length + hood.height + horn.reach + slit.length + gap.width)


syr3.lm<-lm(data=df_syr, fruits ~ gyn.width + hood.length + hood.height + horn.reach + slit.length + gap.width)
plot(syr3.lm)

summary(syr3.lm)

Call:
lm(formula = fruits ~ gyn.width + hood.length + hood.height + 
    horn.reach + slit.length + gap.width, data = df_syr)

Residuals:
   Min     1Q Median     3Q    Max 
-9.142 -3.143 -0.369  2.709 15.094 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)  
(Intercept)   -30.09      17.79   -1.69    0.100 .
gyn.width     102.33      75.42    1.36    0.184  
hood.length    -9.25      25.11   -0.37    0.715  
hood.height    11.39      23.68    0.48    0.633  
horn.reach     85.09      39.04    2.18    0.036 *
slit.length    -1.08      77.23   -0.01    0.989  
gap.width    -107.72      72.86   -1.48    0.148  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 5.11 on 35 degrees of freedom
  (3 observations deleted due to missingness)
Multiple R-squared:  0.292, Adjusted R-squared:  0.17 
F-statistic:  2.4 on 6 and 35 DF,  p-value: 0.0475
options(digits=3)
tidy(syr3.lm, conf.int = TRUE)
glance(syr3.lm)

lm.beta.syr3 <- lm.beta(syr3.lm)
tidy(lm.beta.syr3, conf.int = TRUE)

Still lots of unexplained variation. Only one predictor has much influence (horn reach)

What would you need to check in doing analyses on three different fitness components as response variables?

Check that they aren’t correlated with each other.

cor(df_syr[,c('removals.per.flower', 'insertions.per.flower', 'fruits')])
                      removals.per.flower insertions.per.flower fruits
removals.per.flower                 1.000                 0.499     NA
insertions.per.flower               0.499                 1.000     NA
fruits                                 NA                    NA      1

OK; one r=0.5

What do you conclude about the floral traits’ influence on fitness components of this species?

Floral traits affect two of the three components - hood parameters are positively related to pollen removal rates, and horn reach is linked to higher fruit numbers.

Now have a look at the data for the more common species Asclepias viridiflora*

Remember that one floral trait, hood height, isn’t relevant for flowers of this species

Just show big code block here.

#Run diagnostics
scatterplotMatrix(data=df_vir,~ removals.per.flower + gyn.width + hood.length + hood.height + slit.length + gap.width)

cor(df_vir[,c('gyn.width', 'hood.length', 'hood.height', 'slit.length', 'gap.width')])
            gyn.width hood.length hood.height slit.length gap.width
gyn.width       1.000      0.1286      0.1672     0.24030   0.25034
hood.length     0.129      1.0000      0.1527     0.08002  -0.04063
hood.height     0.167      0.1527      1.0000     0.22310  -0.04564
slit.length     0.240      0.0800      0.2231     1.00000   0.00707
gap.width       0.250     -0.0406     -0.0456     0.00707   1.00000
vif(lm(data=df_vir, removals.per.flower ~ gyn.width + hood.length + hood.height + slit.length + gap.width))
  gyn.width hood.length hood.height slit.length   gap.width 
       1.17        1.04        1.09        1.10        1.08 
vir1.lm<-lm(data=df_vir, removals.per.flower ~ gyn.width + hood.length + hood.height + slit.length + gap.width)
plot(vir1.lm)

summary(vir1.lm)

Call:
lm(formula = removals.per.flower ~ gyn.width + hood.length + 
    hood.height + slit.length + gap.width, data = df_vir)

Residuals:
   Min     1Q Median     3Q    Max 
-2.034 -0.463  0.041  0.526  1.549 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)  
(Intercept)    1.510      1.427    1.06     0.29  
gyn.width     -0.773      4.754   -0.16     0.87  
hood.length    3.647      5.684    0.64     0.52  
hood.height    3.613      1.750    2.06     0.04 *
slit.length   -2.939      6.213   -0.47     0.64  
gap.width     -6.765      6.462   -1.05     0.30  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.747 on 206 degrees of freedom
Multiple R-squared:  0.0315,    Adjusted R-squared:  0.00803 
F-statistic: 1.34 on 5 and 206 DF,  p-value: 0.248
#Look at results of model fitting

options(digits=3)
tidy(vir1.lm, conf.int = TRUE)
glance(vir1.lm)

#Get standardized coefficients with lmbeta

lm.beta.vir1 <- lm.beta(vir1.lm)
tidy(lm.beta.vir1, conf.int = TRUE)

#Run through same sequence for the other two life-history traits

scatterplotMatrix(data=df_vir,~ insertions.per.flower + gyn.width + hood.length + hood.height + slit.length + gap.width)


vir2.lm<-lm(data=df_vir, insertions.per.flower ~ gyn.width + hood.length + hood.height + slit.length + gap.width)
plot(vir2.lm)

summary(vir2.lm)

Call:
lm(formula = insertions.per.flower ~ gyn.width + hood.length + 
    hood.height + slit.length + gap.width, data = df_vir)

Residuals:
    Min      1Q  Median      3Q     Max 
-0.2910 -0.1235 -0.0343  0.1059  0.4594 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)  
(Intercept)   0.0259     0.3269    0.08    0.937  
gyn.width    -0.4290     1.0889   -0.39    0.694  
hood.length  -0.0425     1.3019   -0.03    0.974  
hood.height   0.2837     0.4007    0.71    0.480  
slit.length   2.8794     1.4231    2.02    0.044 *
gap.width    -2.3061     1.4802   -1.56    0.121  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.171 on 206 degrees of freedom
Multiple R-squared:  0.0393,    Adjusted R-squared:  0.016 
F-statistic: 1.68 on 5 and 206 DF,  p-value: 0.14
options(digits=3)
tidy(vir2.lm, conf.int = TRUE)
glance(vir2.lm)

lm.beta.vir2 <- lm.beta(vir2.lm)
tidy(lm.beta.vir2, conf.int = TRUE)


scatterplotMatrix(data=df_vir,~ fruits + gyn.width + hood.length + hood.height + slit.length + gap.width)


vir3.lm<-lm(data=df_vir, fruits ~ gyn.width + hood.length + hood.height + slit.length + gap.width)
plot(vir3.lm)

summary(vir3.lm)

Call:
lm(formula = fruits ~ gyn.width + hood.length + hood.height + 
    slit.length + gap.width, data = df_vir)

Residuals:
   Min     1Q Median     3Q    Max 
-3.864 -1.570 -0.405  0.974 14.857 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)    -3.46       4.73   -0.73  0.46489    
gyn.width      15.72      15.90    0.99  0.32412    
hood.length    64.00      18.85    3.40  0.00083 ***
hood.height    -2.04       6.02   -0.34  0.73444    
slit.length    10.27      20.78    0.49  0.62171    
gap.width      -7.30      21.49   -0.34  0.73432    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 2.46 on 199 degrees of freedom
  (7 observations deleted due to missingness)
Multiple R-squared:  0.0681,    Adjusted R-squared:  0.0447 
F-statistic: 2.91 on 5 and 199 DF,  p-value: 0.0147
options(digits=3)
tidy(vir3.lm, conf.int = TRUE)
glance(vir3.lm)

lm.beta.vir3 <- lm.beta(vir3.lm)
tidy(lm.beta.vir3, conf.int = TRUE)

Hood height effect detected for removals per flower, but model rsq<1%, and standardized estimate not particularly large Slit length detected for insertions per flower, but rsq again very low, 1.6%, and standardized estimate low Fruits also with poor model fit, rsq <5%; highly “significant” effect of hood length detected. Standardized estimates low, 0.24 - strongest for this species.

What do you conclude about the role of floral traits in these two species?

Different floral traits matter - the best predictor of each fitness component is different between the two species.

Is there anything you’d be cautious about in making this comparison?

Different detection abilities for the two species, because one is commoner than the other, and easier to get a large sample.

Watch out for students using statistical signficance only to guide conclusions

Different sensitivies probably mean that two of the 3 effects for A. viridiflora wouldn’t have been detected with the A. syriaca sample size

Standardized coefficients suggest weaker overall effects in A. viridiflora

C

Recall the sengi example from Chapter 5 (or go back and look at it ;-)). The research questions are about family differences. Could also look at this relationship between sengis and the rest. There are 2 or 3 groups of insectivores, sengis and closely (afrotherian) and more distantly (laurasiatherian) species, and the research question is about where sengis fit. We can frame this as 2 or 3 questions.

  1. Does the new species (udzugwensis) fit within the pattern of other sengi?

  2. Are sengi different from the other small insectivores in their brain size?

    1. sengi vs all others, or

    2. sengi vs closely-related vs distantly related insectivores

Get started by loading the kaufman data. In Chapter 5, you should have decided that log-transforming both variables was sensible, so lets also start by defining new variables logbrain and log body. That will make the coding tidier, without having to log things repeatedly.

df <- read.csv("data/kaufman.csv")
df$logbrain <- log(df$brainmass)
df$logbody <- log(df$bodymass)

For the first question, cast your mind back to Chapter 6. How would you decide whether the new species is unusual?

Use the existing species to calculate a linear regression, then calculate the predicted value for a species of the body mass of R. udzugwensis.

sengi <- filter(df, family == "Macroscelididae")
sengi
sengi.lm <- lm(data=filter(sengi, species != "udzugwensis"), logbrain ~ logbody)
glance(sengi.lm)
tidy(sengi.lm)

# Now predict values for *R. udzu..*

predict(sengi.lm, data.frame(logbody = c(6.565)), interval="prediction", se=T)
$fit
   fit lwr  upr
1 8.91 8.5 9.32

$se.fit
[1] 0.0605

$df
[1] 2

$residual.scale
[1] 0.0723

Predicted log brain mass is 8.91, and the prediction interval is 8.50 - 9.31, so this species is pretty much smack on the line.

Now lets compare sengis to the other insectivores. Use three groups for comparison (sengi, Afrotherian and Laurasiatherian). These groups are defined in the variable relation

** You could make this comparison in two ways:

  • fit a linear model including the groups and log body mass, i.e. an analysis of covariance
  • look at the patterns in the residuals for the relationship between log-brain and log-body

**Before you start, are there any things to check in the original data, linked to the assumptions of the linear model you’ll fit?

The other important thing to note is that if we’re looking to compare sengis and other groups formally, we need to be careful about the ranges of body sizes. Sengi species start around the middle of the range, from 45g up. We’d probably restrict our comparison to species around this size - exclude those with body mass less than a threshold. Threshold would be arbitrary, but most people would choose 40 or 45g

ggplot(data=df, aes(x=log(bodymass), y=log(brainmass), colour = relation, shape = relation))+
  geom_point()+ theme_light() + scale_color_uchicago()

Analysis of covariance

Outline the steps you’ll take

  1. Fit complex model allowing slopes to vary among groups
  2. Assess whether the groups*logbody term should remain in the model
  3. If relationships can be treated as parallel, run simpler model with groups and logbody
  4. Examine results and decide whether to do any comparisons among groups
df$relation<-factor(df$relation)
df$relation2<-factor(df$relation2)
contrasts = list (relation = contr.sum)
# Create subset of file. We could keep running the filter each time we run the model, but doing it once is tidier.
df2 <- filter(df, bodymass > 40)
lm2<-aov(logbrain~logbody + relation + logbody*relation, data = df2)
glance(lm2)
tidy(lm2)
Anova(lm2, type='III')
Anova Table (Type III tests)

Response: logbrain
                 Sum Sq Df F value  Pr(>F)    
(Intercept)        9.49  1  126.81 7.8e-11 ***
logbody            3.41  1   45.58 6.9e-07 ***
relation           0.05  2    0.30    0.74    
logbody:relation   0.07  2    0.45    0.64    
Residuals          1.72 23                    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

logbody*relation term contributing little to model fit, so run simpler model

lm3<-aov(logbrain~logbody + relation, data = df2)
glance(lm3)
tidy(lm3)
Anova(lm3, type='III')
Anova Table (Type III tests)

Response: logbrain
            Sum Sq Df F value  Pr(>F)    
(Intercept)  23.29  1     326 7.6e-16 ***
logbody      12.99  1     182 5.8e-13 ***
relation      1.86  2      13 0.00013 ***
Residuals     1.79 25                    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
#Get adjusted means
library(effects)
adjmeans <- effect("relation", lm3, se=TRUE)
summary(adjmeans)

 relation effect
relation
    afrotherian laurasiatherian           sengi 
           7.17            7.34            7.91 

 Lower 95 Percent Confidence Limits
relation
    afrotherian laurasiatherian           sengi 
           7.01            7.18            7.66 

 Upper 95 Percent Confidence Limits
relation
    afrotherian laurasiatherian           sengi 
           7.34            7.50            8.16 
ggplot(data=df2, aes(x=log(bodymass), y=log(brainmass), group= relation, colour = relation, shape = relation))+
  geom_point()+ geom_smooth(method=lm) + theme_light() + scale_color_uchicago()

No need to proceed further. We conclude that slopes differ between the groups, and sengi are clearly above the other two. The other two groups are close, with abutting confidence intervals around the adjusted means.

Use residuals from a regression of all data and compare residuals between groups
lm4 <- lm(log(brainmass) ~ log(bodymass), data=df2)
df3 <- cbind(df2, lm4$residuals)
head(df3,10)
boxplot(lm4$residuals ~ relation, data = df3) 

lm5 <- aov (lm4$residuals ~ relation, data = df3)
summary(lm5)
            Df Sum Sq Mean Sq F value  Pr(>F)    
relation     2   1.80   0.901    12.6 0.00015 ***
Residuals   26   1.85   0.071                    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Pattern also clear here, just from box plots

D

We’ll return to the elephant seal example in the study by LaBoeuf et al., and see whether body weight plays any role in foraging. In Chapter 5, you should have noticed that while the focus of the initial analysis was on the relationship between distance travelled and time spend on the foraging grounds, the authors recorded weight on departure for each animal. Your exploratory data analysis should have shown a relationship between body weight and the original predictor and response variables. Now try and make some sense of what’s going on here.

#Get the data file back
df <- read.csv("data/leboeuf.csv")
head(df,10)

Think about how body mass might influence distance travelled and how it might contribute to time on foraging areas

Mass could easily be linked to swimming speed, allowing larger animals to forage for longer. We could even get more complicated and speculate whether larger males may spend more time maintaining dominance on the shore, so might forage less. In either case, the two variables could be linked.

Body mass might also reflect overall condition, and, for example, males in poorer condidtion might need to spend longer feeding.

How will you assess whether including body weight as a second predictor helps us understand feeding time better?

Time on foraging = β0 + β1 Distance travelled + β2 Body weight

Need to check collinearity - use VIF

Check residuals

Check linearity

cor(df[,c('distance','departwt')])
         distance departwt
distance        1       NA
departwt       NA        1
laboeuf1.lm <- lm (FFAduration~ distance + departwt, data = df)
vif(laboeuf1.lm)
distance departwt 
    4.34     4.34 
plot(laboeuf1.lm)

VIF value not small, but below the common threshold of 5

Nothing dramatic in residuals, though points 13, 14, and 16 have large residuals

augment(laboeuf1.lm)

Fit the appropriate model to the data, interpret the results, and explain whether body weight helps us.

Get the equation

\[ \operatorname{FFAduration} = \alpha + \beta_{1}(\operatorname{distance}) + \beta_{2}(\operatorname{departwt}) + \epsilon \]

glance(laboeuf1.lm)
tidy(laboeuf1.lm)

Get standarised regression coefficients

Use lm.beta function from lm.beta package

lm.beta.laboeuf <- lm.beta(laboeuf1.lm)
tidy(lm.beta.laboeuf, conf.int = TRUE)
glance(lm.beta.laboeuf)

Show the partial regression (added-variable) plots

avPlots(laboeuf1.lm, ask=F)

Conclusion is that there’s a strong statistical relationship. The model explains around 60% of variation, and that effect is largely associated with distance travelled. Time on foraging grounds falls sharply with distance.

Departure rate plays little role.

Is there anything else you might look at?

The relationship of distance to both response and the other predictor might make it worth looking for a non-additive model

laboeuf2.lm <- lm (FFAduration~ distance + departwt + distance*departwt, data = df)
vif(laboeuf2.lm)
there are higher-order terms (interactions) in this model
consider setting type = 'predictor'; see ?vif
         distance          departwt distance:departwt 
             91.6              41.6             225.4 
# High vif values, as expected, so centre predictors and run again
df$cdistance <- scale(df$distance, center=TRUE, scale=FALSE)
df$cdepartwt <- scale(df$departwt, center=TRUE, scale=FALSE)
laboeuf3.lm <- lm (FFAduration~ cdistance + cdepartwt + cdistance*cdepartwt, data = df)
vif(laboeuf3.lm)
there are higher-order terms (interactions) in this model
consider setting type = 'predictor'; see ?vif
          cdistance           cdepartwt cdistance:cdepartwt 
               7.89                8.43                1.95 
lm.beta.laboeuf3 <- lm.beta(laboeuf3.lm)
tidy(lm.beta.laboeuf3, conf.int = TRUE)
glance(lm.beta.laboeuf3)
NA

Actually a worse model; more issues with VIF, adjusted r-sq lower, and combined term contributes nothing

LS0tCnRpdGxlOiAiQ2ggOCBleGVyY2lzZXMiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdGhlbWU6IGZsYXRseQotLS0KClRoZSBhaW0gb2YgdGhlc2UgZXhlcmNpc2VzIGlzIHRvIGltcHJvdmUgeW91ciBhYmlsaXR5IHRvIGRlYWwgd2l0aCBtdWx0aS1wcmVkaWN0b3IgbGluZWFyIG1vZGVscyB3aGVyZSB0aGUgcHJlZGljdG9ycyBhcmUgYWxsIGNvbnRpbnVvdXMgb3IgYSBtaXh0dXJlIG9mIGNvbnRpbnVvdXMgYW5kIGNhdGVnb3JpY2FsLgoKYGBge3IgZWNobz1GQUxTRSwgcmVzdWx0cz0naGlkZSd9CnNvdXJjZSgiUi9saWJyYXJpZXMuUiIpCmxpYnJhcnkobG0uYmV0YSkKYGBgCgojIyMgQQoKTGV0J3MgY29udGludWUgd2l0aCB0aGUgZXhhbXBsZSBmcm9tIENoYXB0ZXIgNyBhc3Nlc3Npbmcgd2hldGhlciBkaWV0YXJ5IHN1cHBsZW1lbnRzIGltcHJvdmUgdGhlIHBlcmNlaXZlZCBoZWFsdGggb2YgZG9ncyB3aXRoIG9zdGVvYXJ0aHJpdGlzLiBUaGUgbW9kZWwgd2UgZm9jdXNlZCBvbiBhdCB0aGUgZW5kIG9mIHRoYXQgZXhlcmNpc2Ugd2FzIG9uZSBtb2RlbGxpbmcgdGhlIHBhaW4gaW5kZXggb2YgZG9ncyBhZnRlciA2MCBkYXlzIGFzIGEgZnVuY3Rpb24gb2Ygd2hldGhlciB0aGV5IHJlY2VpdmVkIGRpZXRhcnkgc3VwcGxlbWVudHMgb3IgYSBwbGFjZWJvIGFuZCB0aGUgc2V4IG9mIHRoZSBkb2cuIFRoZSBkb2dzIHVuYXZvaWRhYmx5IHZhcmllZCBpbiBib2R5IHdlaWdodCwgcmFuZ2luZyBmcm9tIDE0LTQ3IGtnLiBUbyBwYXJ0bHkgYWNjb3VudCBmb3IgdGhpcywgdGhlIGF1dGhvcnMgYWRqdXN0ZWQgdGhlIGRvc2VzIHRvIGEgY29uc3RhbnQgYW1vdW50IHBlciBrZyBvZiBib2R5IHdlaWdodC4gSG93ZXZlciwgeW91IGNhbiBwcm9iYWJseSB0aGluayBvZiBhIHJhbmdlIG9mIHdheXMgaW4gd2hpY2ggd2VpZ2h0IG1pZ2h0IGFmZmVjdCBvc3Rlb2FydGhyaXRpcy4gVGhlIG1vZGVsIHVzaW5nIHRyZWF0bWVudCBhbmQgc2V4IGZpdHRlZCB0aGUgZGF0YSBmYWlybHkgd2VsbCwgd2l0aCByXjJeIGFyb3VuZCAwLjYuIFdlIGRldGVjdGVkIGEgc3Ryb25nIHRyZWF0bWVudCBlZmZlY3QsIGJ1dCBpdCBpcyBwb3NzaWJsZSB0aGF0IGlmIHdlIHJlZHVjZWQgYmFja2dyb3VuZCBub2lzZSwgd2UgbWlnaHQgc2VlIHNleC1zcGVjaWZpYyByZXNwb25zZXMgYW5kIHdlJ2QgYWxzbyBnZXQgYSBtb3JlIHByZWNpc2UgZXN0aW1hdGUgb2YgdGhlIGVmZmVjdHMuCgpUaGluayBhYm91dCB0aGUgc3RlcHMgeW91J2QgdGFrZSB0byBzZWUgaWYgaXQgd291bGQgYmUgaGVscGZ1bCB0byBpbmNsdWRlIGJvZHkgd2VpZ2h0IGluIHRoZSBtb2RlbCwgdGhlbiBnbyBiYWNrIHRvIHRoZSBkYXRhIGFuZCBydW4gdGhlIGFuYWx5c2lzLgoKPiBTdGFydCBieSByZWxvYWRpbmcgdGhlIGRhdGEgZmlsZSBmcm9tIENoYXB0ZXIgNyBleGVyY2lzZXMKCmBgYHtyfQpkZiA8LSByZWFkLmNzdigiZGF0YS9tYXJ0ZWxsby5jc3YiKQpoZWFkKGRmLDEwKQojVGlkeSB1cCB0aGUgbmFtZXMgaWYgb3JpZ2luYWwgZGF0YSB1c2VkCiNkZl9uYW1lczwtYyhHUk9VUD0iZ3JvdXAiLCBzdGVyaWxpenphdG8gPSAic3RlciIsIEJDUyA9ICJiY3MiLCBQRVNPLktHID0gInd0IiwgRVRBID0gImV0YSIsIFJBWlpBID0gImJyZWVkIiwKIyAgICAgICAgICAgICAgICAgIEhDUEkgPSAiaGNwMCIsIEhDUEkuNCA9ICJoY3A0MCIsIEhDUC42ID0gImhjcDYwIiwKIyAgICAgICAgICAgICAgICAgIFNFR05JLk9BLlZFVCA9ICJ2ZXQwIiwgU0VHTkkuQU8uVkVULjQgPSAidmV0NDAiLCBTRUdOSS5BTy5WRVQuNiA9ICJ2ZXQ2MCIpCiNkZjwtcmVuYW1lKGRmLCBhbGxfb2YoZGZfbmFtZXMpKQojIG1ha2Ugc2V4LCB0cmVhdG1lbnQsIGFuZCBzdGVyaWxpemF0aW9uIGZhY3RvcnMKZGYkc2V4PC1hcy5mYWN0b3IoZGYkc2V4KQpkZiRncm91cDwtYXMuZmFjdG9yKGRmJGdyb3VwKQpkZiRzdGVyPC1hcy5mYWN0b3IoZGYkc3RlcikKI3NldCBjb250cmFzdHMgdG8gc3VtCmNvbnRyYXN0cz1saXN0KGdyb3VwPWNvbnRyLnN1bSwgc2V4PWNvbnRyLnN1bSwgc3Rlcj1jb250ci5zdW0pCmBgYAoKPiBUaGUgbW9kZWwgd2UgaGFkIGluIHRoYXQgZXhlcmNpc2UgbGlua2VkIGZpbmFsIG93bmVyLWFzc2Vzc2VkIGhlYWx0aCAoaGNwNjApIHRvIHRyZWF0bWVudCAoZ3JvdXApLCBzZXgsIGFuZCBwb3NzaWJseSBzdGVyaWxpemF0aW9uIHN0YXR1cyAoc3RlcikuCgo+IEZvciBzaW1wbGljaXR5LCBsZXQncyB1c2UgdGhlIHRyZWF0bWVudC9zZXggbW9kZWwsIGFuZCBzdGFydCBieSBydW5uaW5nIGl0IGZvciBjb21wYXJpc29uCgpgYGB7cn0KbWFydGVsbG8ubG0gPC0gbG0oaGNwNjB+Z3JvdXAqc2V4LCBkYXRhPWRmKQpzdW1tYXJ5KG1hcnRlbGxvLmxtKQplbW1lYW5zKG1hcnRlbGxvLmxtLCB+IGdyb3VwOnNleCkKYGBgCgo+IFN0YXJ0IGJ5IGNoZWNraW5nIGNvdmFyaWF0ZSByYW5nZXMKCmBgYHtyfQpib3hwbG90KHd0fmdyb3VwKnNleCwgZGF0YT1kZikKYGBgCgo+IFNvbWUgc2xpZ2h0IGRpZmZlcmVuY2VzLCBpbmNsdWRpbmcgc2V4LWJhc2VkIG9uZXMsIGJ1dCByYW5nZXMgb3ZlcmxhcCwgc28gcHJvYmFibHkgT0sKCj4gTm93IHJ1biBtb2RlbCBhbmQgbG9vayBhdCByZXNpZHVhbHMuIFRyeSBtb2RlbCB3aXRoIDMgc2xvcGVzIHRlcm1zIGZpcnN0CgpgYGB7cn0KbWFydGVsbG8yLmxtIDwtIGxtKGhjcDYwfmdyb3VwKnNleCp3dCwgZGF0YT1kZikKcGxvdChtYXJ0ZWxsbzIubG0pCnN1bW1hcnkobWFydGVsbG8yLmxtKQpBbm92YShtYXJ0ZWxsbzIubG0sIHR5cGU9J0lJSScpCmBgYAoKPiBOb3RoaW5nIGhlcmUgdG8gc3VnZ2VzdCB0aGF0IHNsb3BlcyBhcmUgZGlmZmVybnQgYWNyb3NzIHRoZSBjYXRlZ29yaWNhbCBwcmVkaWN0b3JzLCBzbyBsZXRzIHJ1biBhbiBpbnRlcmNlcHRzIG1vZGVsCgpgYGB7cn0KbWFydGVsbG8zLmxtIDwtIGxtKGhjcDYwfmdyb3VwKnNleCt3dCwgZGF0YT1kZikKcGxvdChtYXJ0ZWxsbzMubG0pCnN1bW1hcnkobWFydGVsbG8zLmxtKQpBbm92YShtYXJ0ZWxsbzMubG0sIHR5cGU9J0lJSScpCmVtbWVhbnMobWFydGVsbG8zLmxtLCB+Z3JvdXB8c2V4KQpgYGAKCj4gQm9keSB3ZWlnaHQgcGxheXMgdmVyeSBsaXR0bGUgcm9sZSBoZXJlLiBBZGRpbmcgaXQgdG8gdGhlIG1vZGVsIG9ubHkgcmFpc2VzIHRoZSB2YXJpYW5jZSBleHBsYWluZWQgYnkgMC4xJSwgYW5kIG5vdCBzdXJwcmlzaW5nbHksIHRoZSBlc3RpbWF0ZWQgZWZmZWN0cyBoYXZlbid0IGNoYW5nZWQgbXVjaCBlaXRoZXIuCgojIyMgQgoKW0xhUm9zYSBhbmQgQ29ubm9yIChBbWVyLiBKLiBCb3QuLCAyMDE3KV0oaHR0cHM6Ly9kb2kub3JnLzEwLjM3MzIvYWpiLjE2MDAzMjgpIGV4YW1pbmVkIGVmZmVjdHMgb2Ygc2V2ZXJhbCBmbG9yYWwgdHJhaXRzIG9uIGZpdG5lc3MgY29tcG9uZW50cyBvZiBtaWxrd2VlZHMsICpBc2NsZXBpYXMqIHNwcC4gVGhlIGZpdG5lc3MgY29tcG9uZW50cyB3ZXJlIG1hbGUgYW5kIGZlbWFsZSBwb2xsaW5hdGlvbiBzdWNjZXNzIGFuZCBmZW1hbGUgcmVwcm9kdWN0aXZlIHN1Y2Nlc3MuCgpJbiB0aGUgcGFwZXIsIHRoZWlyIGFuYWx5c2lzIGZvY3VzZWQgb24gNiBwcmVkaWN0b3JzLCBUaGV5IG1lYXN1cmVkIHNpeCBmbG9yYWwgdHJhaXRzLCBhbHRob3VnaCBvbmUgb2YgdGhlbSwgaG9vZCBoZWlnaHQsIHdhcyBub3QgcmVsZXZhbnQgZm9yICoqQXNjbGVwaWFzIHZpcmlkaWZsb3JhKiosIHdoaWNoIHdhcyB0aGUgc3BlY2llcyB3aXRoIHRoZSBsYXJnZXN0IHNhbXBsZSBzaXplLiAtIGd5bm9zdGVnaXVtIHdpZHRoLCAtIGhvb2QgbGVuZ3RoLCAtIGhvb2QgaGVpZ2h0LCAtIGhvcm4gcmVhY2gsIC0gc2xpdCBsZW5ndGgsIGFuZCAtIGdhcCB3aWR0aAoKVGhlaXIgRmlndXJlIDIgc2hvd3Mgd2hhdCB0aGVzZSBtZWFzdXJlbWVudHMgcmVwcmVzZW50IG9uIGZsb3dlcnMuCgpUaGUgZGF0YSBhcmUgYXZhaWxhYmxlIGZyb20gRHJ5YWQgW2hlcmVdKGh0dHA6Ly9kb2kub3JnLzEwLjUwNjEvZHJ5YWQubjgxZzEpLgoKVGhlIGZsb3JhbCB0cmFpdHMgd2VyZSBkaWZmZXJlbnQgYmV0d2VlbiBzcGVjaWVzLCBzbyBkYXRhIGFuYWx5c2lzIHdvdWxkIHJlcXVpcmUgZWFjaCBzcGVjaWVzIHRvIGJlIGFuYWx5emVkIHNlcGFyYXRlbHkgb3IgZm9yIHRoZSBtZWFzdXJlbWVudHMgdG8gYmUgc3RhbmRhcmRpemVkIHdpdGhpbiBlYWNoIHNwZWNpZXMuCgpgYGB7cn0KZGYgPC0gcmVhZC5jc3YoImRhdGEvbGFyb3NhLmNzdiIpCmhlYWQoZGYsMTApCmRmX3N5cjwtc3Vic2V0KGRmLHNwZWNpZXM9PSJBc3lyIikKZGZfdmlyPC1zdWJzZXQoZGYsIHNwZWNpZXM9PSdBdmlyJykKCmBgYAoKRml0bmVzcyBjb21wb25lbnQgZXN0aW1hdGVzIHdlcmUgcmVsYXRpdml6ZWQgYnkgZGl2aWRpbmcgYnkgdGhlIG1lYW4sIGFuZCB0aGUgdHJhaXRzIHdlcmUgc3RhbmRhcmRpemVkIHRvIGEgbWVhbiBvZiB6ZXJvIGFuZCBzdGFuZGFyZCBkZXZpYXRpb24gb2Ygb25lLgoKIyMjIyBTdGFydCBieSBsb29raW5nIGF0ICoqQS4gc3lyaWFjYSoqLCB0aGVuIGZvciBjb21wYXJpc29uLCBsb29rIGF0IGhvdyB0aGVzZSBmbG9yYWwgdHJhaXRzIGFmZmVjdCAqKkEuIHZpcmlkaWZsb3JhKioKCipGaXJzdCBsb29rIGF0IHRoZSByZW1vdmFscyBwZXIgZmxvd2VyKgoKKldoYXQgY2hlY2tzIHNob3VsZCB5b3UgZG8gYmVmb3JlIGFzc2Vzc2luZyB0aGUgcHJlZGljdG9ycycgZWZmZWN0cz8qCgpgYGB7cn0Kc2NhdHRlcnBsb3RNYXRyaXgoZGF0YT1kZl9zeXIsfiByZW1vdmFscy5wZXIuZmxvd2VyICsgZ3luLndpZHRoICsgaG9vZC5sZW5ndGggKyBob29kLmhlaWdodCArIGhvcm4ucmVhY2ggKyBzbGl0Lmxlbmd0aCArIGdhcC53aWR0aCkKY29yKGRmX3N5clssYygnZ3luLndpZHRoJywgJ2hvb2QubGVuZ3RoJywgJ2hvb2QuaGVpZ2h0JywgJ2hvcm4ucmVhY2gnLCAnc2xpdC5sZW5ndGgnLCAnZ2FwLndpZHRoJyldKQp2aWYobG0oZGF0YT1kZl9zeXIsIHJlbW92YWxzLnBlci5mbG93ZXIgfiBneW4ud2lkdGggKyBob29kLmxlbmd0aCArIGhvb2QuaGVpZ2h0ICsgaG9ybi5yZWFjaCArIHNsaXQubGVuZ3RoICsgZ2FwLndpZHRoKSkKYGBgCgo+IERpYWdub3N0aWNzIE9LCgpgYGB7cn0Kc3lyMS5sbTwtbG0oZGF0YT1kZl9zeXIsIHJlbW92YWxzLnBlci5mbG93ZXIgfiBneW4ud2lkdGggKyBob29kLmxlbmd0aCArIGhvb2QuaGVpZ2h0ICsgaG9ybi5yZWFjaCArIHNsaXQubGVuZ3RoICsgZ2FwLndpZHRoKQpwbG90KHN5cjEubG0pCnN1bW1hcnkoc3lyMS5sbSkKYGBgCgoqSWYgeW91J3JlIGhhcHB5IHdpdGggeW91ciBwcmUtZmxpZ2h0IGNoZWNrcywgZml0IHRoZSBtb2RlbCBhbmQgbWFrZSBzb21lIGNvbmNsdXNpb25zIGFib3V0IHRoZSBlZmZlY3RzIG9mIGVhY2ggcHJlZGljdG9yLCBpbmNsdWRpbmcgYW55IG5vdGVzIG9mIGNhdXRpb24qCgo+IExvb2sgYXQgcmVzdWx0cyBvZiBtb2RlbCBmaXR0aW5nCgpgYGB7cn0Kb3B0aW9ucyhkaWdpdHM9MykKdGlkeShzeXIxLmxtLCBjb25mLmludCA9IFRSVUUpCmdsYW5jZShzeXIxLmxtKQpgYGAKCj4gR2V0IHN0YW5kYXJkaXplZCBjb2VmZmljaWVudHMgd2l0aCBsbWJldGEKCmBgYHtyfQpsbS5iZXRhLnN5cjEgPC0gbG0uYmV0YShzeXIxLmxtKQp0aWR5KGxtLmJldGEuc3lyMSwgY29uZi5pbnQgPSBUUlVFKQpgYGAKCiMjIyMgUnVuIHRocm91Z2ggc2FtZSBzZXF1ZW5jZSBmb3IgdGhlIG90aGVyIHR3byBsaWZlLWhpc3RvcnkgdHJhaXRzCgo+IEZpcnN0IGRvIGluc2VydGlvbnMucGVyLmZsb3dlcgoKYGBge3J9CnNjYXR0ZXJwbG90TWF0cml4KGRhdGE9ZGZfc3lyLH4gaW5zZXJ0aW9ucy5wZXIuZmxvd2VyICsgZ3luLndpZHRoICsgaG9vZC5sZW5ndGggKyBob29kLmhlaWdodCArIGhvcm4ucmVhY2ggKyBzbGl0Lmxlbmd0aCArIGdhcC53aWR0aCkKCnN5cjIubG08LWxtKGRhdGE9ZGZfc3lyLCBpbnNlcnRpb25zLnBlci5mbG93ZXIgfiBneW4ud2lkdGggKyBob29kLmxlbmd0aCArIGhvb2QuaGVpZ2h0ICsgaG9ybi5yZWFjaCArIHNsaXQubGVuZ3RoICsgZ2FwLndpZHRoKQpwbG90KHN5cjIubG0pCnN1bW1hcnkoc3lyMi5sbSkKCm9wdGlvbnMoZGlnaXRzPTMpCnRpZHkoc3lyMi5sbSwgY29uZi5pbnQgPSBUUlVFKQpnbGFuY2Uoc3lyMi5sbSkKCmxtLmJldGEuc3lyMiA8LSBsbS5iZXRhKHN5cjIubG0pCnRpZHkobG0uYmV0YS5zeXIyLCBjb25mLmludCA9IFRSVUUpCmBgYAoKPiBNb2RlbCBleHBsYWlucyBsZXNzIG9mIHZhcmlhdGlvbjsgb3ZlcmFsbCByc3EgbXVjaCBsb3dlci4gU2FtZSB0d28gcHJlZGljdG9ycyBoYXZlIGhpZ2hlc3Qgc3RhbmRhcmRpemVkIGNvZWZmaWNpZW50cywgYnV0IGxhcmdlbHkgbm9pc2UgaGVyZQoKPiBOb3cgZG8gbnVtYmVycyBvZiBmcnVpdHMKCmBgYHtyfQpzY2F0dGVycGxvdE1hdHJpeChkYXRhPWRmX3N5cix+IGZydWl0cyArIGd5bi53aWR0aCArIGhvb2QubGVuZ3RoICsgaG9vZC5oZWlnaHQgKyBob3JuLnJlYWNoICsgc2xpdC5sZW5ndGggKyBnYXAud2lkdGgpCgpzeXIzLmxtPC1sbShkYXRhPWRmX3N5ciwgZnJ1aXRzIH4gZ3luLndpZHRoICsgaG9vZC5sZW5ndGggKyBob29kLmhlaWdodCArIGhvcm4ucmVhY2ggKyBzbGl0Lmxlbmd0aCArIGdhcC53aWR0aCkKcGxvdChzeXIzLmxtKQpzdW1tYXJ5KHN5cjMubG0pCgpvcHRpb25zKGRpZ2l0cz0zKQp0aWR5KHN5cjMubG0sIGNvbmYuaW50ID0gVFJVRSkKZ2xhbmNlKHN5cjMubG0pCgpsbS5iZXRhLnN5cjMgPC0gbG0uYmV0YShzeXIzLmxtKQp0aWR5KGxtLmJldGEuc3lyMywgY29uZi5pbnQgPSBUUlVFKQpgYGAKCj4gU3RpbGwgbG90cyBvZiB1bmV4cGxhaW5lZCB2YXJpYXRpb24uIE9ubHkgb25lIHByZWRpY3RvciBoYXMgbXVjaCBpbmZsdWVuY2UgKGhvcm4gcmVhY2gpCgoqV2hhdCB3b3VsZCB5b3UgbmVlZCB0byBjaGVjayBpbiBkb2luZyBhbmFseXNlcyBvbiB0aHJlZSBkaWZmZXJlbnQgZml0bmVzcyBjb21wb25lbnRzIGFzIHJlc3BvbnNlIHZhcmlhYmxlcz8qCgo+IENoZWNrIHRoYXQgdGhleSBhcmVuJ3QgY29ycmVsYXRlZCB3aXRoIGVhY2ggb3RoZXIuCgpgYGB7cn0KY29yKGRmX3N5clssYygncmVtb3ZhbHMucGVyLmZsb3dlcicsICdpbnNlcnRpb25zLnBlci5mbG93ZXInLCAnZnJ1aXRzJyldKQpgYGAKCj4gT0s7IG9uZSByPTAuNQoKKldoYXQgZG8geW91IGNvbmNsdWRlIGFib3V0IHRoZSBmbG9yYWwgdHJhaXRzJyBpbmZsdWVuY2Ugb24gZml0bmVzcyBjb21wb25lbnRzIG9mIHRoaXMgc3BlY2llcz8qCgo+IEZsb3JhbCB0cmFpdHMgYWZmZWN0IHR3byBvZiB0aGUgdGhyZWUgY29tcG9uZW50cyAtIGhvb2QgcGFyYW1ldGVycyBhcmUgcG9zaXRpdmVseSByZWxhdGVkIHRvIHBvbGxlbiByZW1vdmFsIHJhdGVzLCBhbmQgaG9ybiByZWFjaCBpcyBsaW5rZWQgdG8gaGlnaGVyIGZydWl0IG51bWJlcnMuCgojIyMjIE5vdyBoYXZlIGEgbG9vayBhdCB0aGUgZGF0YSBmb3IgdGhlIG1vcmUgY29tbW9uIHNwZWNpZXMgQXNjbGVwaWFzIHZpcmlkaWZsb3JhXCoKClJlbWVtYmVyIHRoYXQgb25lIGZsb3JhbCB0cmFpdCwgaG9vZCBoZWlnaHQsIGlzbid0IHJlbGV2YW50IGZvciBmbG93ZXJzIG9mIHRoaXMgc3BlY2llcwoKPiBKdXN0IHNob3cgYmlnIGNvZGUgYmxvY2sgaGVyZS4KCmBgYHtyfQojUnVuIGRpYWdub3N0aWNzCnNjYXR0ZXJwbG90TWF0cml4KGRhdGE9ZGZfdmlyLH4gcmVtb3ZhbHMucGVyLmZsb3dlciArIGd5bi53aWR0aCArIGhvb2QubGVuZ3RoICsgaG9vZC5oZWlnaHQgKyBzbGl0Lmxlbmd0aCArIGdhcC53aWR0aCkKY29yKGRmX3ZpclssYygnZ3luLndpZHRoJywgJ2hvb2QubGVuZ3RoJywgJ2hvb2QuaGVpZ2h0JywgJ3NsaXQubGVuZ3RoJywgJ2dhcC53aWR0aCcpXSkKdmlmKGxtKGRhdGE9ZGZfdmlyLCByZW1vdmFscy5wZXIuZmxvd2VyIH4gZ3luLndpZHRoICsgaG9vZC5sZW5ndGggKyBob29kLmhlaWdodCArIHNsaXQubGVuZ3RoICsgZ2FwLndpZHRoKSkKCnZpcjEubG08LWxtKGRhdGE9ZGZfdmlyLCByZW1vdmFscy5wZXIuZmxvd2VyIH4gZ3luLndpZHRoICsgaG9vZC5sZW5ndGggKyBob29kLmhlaWdodCArIHNsaXQubGVuZ3RoICsgZ2FwLndpZHRoKQpwbG90KHZpcjEubG0pCnN1bW1hcnkodmlyMS5sbSkKI0xvb2sgYXQgcmVzdWx0cyBvZiBtb2RlbCBmaXR0aW5nCgpvcHRpb25zKGRpZ2l0cz0zKQp0aWR5KHZpcjEubG0sIGNvbmYuaW50ID0gVFJVRSkKZ2xhbmNlKHZpcjEubG0pCgojR2V0IHN0YW5kYXJkaXplZCBjb2VmZmljaWVudHMgd2l0aCBsbWJldGEKCmxtLmJldGEudmlyMSA8LSBsbS5iZXRhKHZpcjEubG0pCnRpZHkobG0uYmV0YS52aXIxLCBjb25mLmludCA9IFRSVUUpCgojUnVuIHRocm91Z2ggc2FtZSBzZXF1ZW5jZSBmb3IgdGhlIG90aGVyIHR3byBsaWZlLWhpc3RvcnkgdHJhaXRzCgpzY2F0dGVycGxvdE1hdHJpeChkYXRhPWRmX3Zpcix+IGluc2VydGlvbnMucGVyLmZsb3dlciArIGd5bi53aWR0aCArIGhvb2QubGVuZ3RoICsgaG9vZC5oZWlnaHQgKyBzbGl0Lmxlbmd0aCArIGdhcC53aWR0aCkKCnZpcjIubG08LWxtKGRhdGE9ZGZfdmlyLCBpbnNlcnRpb25zLnBlci5mbG93ZXIgfiBneW4ud2lkdGggKyBob29kLmxlbmd0aCArIGhvb2QuaGVpZ2h0ICsgc2xpdC5sZW5ndGggKyBnYXAud2lkdGgpCnBsb3QodmlyMi5sbSkKc3VtbWFyeSh2aXIyLmxtKQoKb3B0aW9ucyhkaWdpdHM9MykKdGlkeSh2aXIyLmxtLCBjb25mLmludCA9IFRSVUUpCmdsYW5jZSh2aXIyLmxtKQoKbG0uYmV0YS52aXIyIDwtIGxtLmJldGEodmlyMi5sbSkKdGlkeShsbS5iZXRhLnZpcjIsIGNvbmYuaW50ID0gVFJVRSkKCgpzY2F0dGVycGxvdE1hdHJpeChkYXRhPWRmX3Zpcix+IGZydWl0cyArIGd5bi53aWR0aCArIGhvb2QubGVuZ3RoICsgaG9vZC5oZWlnaHQgKyBzbGl0Lmxlbmd0aCArIGdhcC53aWR0aCkKCnZpcjMubG08LWxtKGRhdGE9ZGZfdmlyLCBmcnVpdHMgfiBneW4ud2lkdGggKyBob29kLmxlbmd0aCArIGhvb2QuaGVpZ2h0ICsgc2xpdC5sZW5ndGggKyBnYXAud2lkdGgpCnBsb3QodmlyMy5sbSkKc3VtbWFyeSh2aXIzLmxtKQoKb3B0aW9ucyhkaWdpdHM9MykKdGlkeSh2aXIzLmxtLCBjb25mLmludCA9IFRSVUUpCmdsYW5jZSh2aXIzLmxtKQoKbG0uYmV0YS52aXIzIDwtIGxtLmJldGEodmlyMy5sbSkKdGlkeShsbS5iZXRhLnZpcjMsIGNvbmYuaW50ID0gVFJVRSkKYGBgCgo+IEhvb2QgaGVpZ2h0IGVmZmVjdCBkZXRlY3RlZCBmb3IgcmVtb3ZhbHMgcGVyIGZsb3dlciwgYnV0IG1vZGVsIHJzcVw8MSUsIGFuZCBzdGFuZGFyZGl6ZWQgZXN0aW1hdGUgbm90IHBhcnRpY3VsYXJseSBsYXJnZSBTbGl0IGxlbmd0aCBkZXRlY3RlZCBmb3IgaW5zZXJ0aW9ucyBwZXIgZmxvd2VyLCBidXQgcnNxIGFnYWluIHZlcnkgbG93LCAxLjYlLCBhbmQgc3RhbmRhcmRpemVkIGVzdGltYXRlIGxvdyBGcnVpdHMgYWxzbyB3aXRoIHBvb3IgbW9kZWwgZml0LCByc3EgXDw1JTsgaGlnaGx5ICJzaWduaWZpY2FudCIgZWZmZWN0IG9mIGhvb2QgbGVuZ3RoIGRldGVjdGVkLiBTdGFuZGFyZGl6ZWQgZXN0aW1hdGVzIGxvdywgMC4yNCAtIHN0cm9uZ2VzdCBmb3IgdGhpcyBzcGVjaWVzLgoKKldoYXQgZG8geW91IGNvbmNsdWRlIGFib3V0IHRoZSByb2xlIG9mIGZsb3JhbCB0cmFpdHMgaW4gdGhlc2UgdHdvIHNwZWNpZXM/KgoKPiBEaWZmZXJlbnQgZmxvcmFsIHRyYWl0cyBtYXR0ZXIgLSB0aGUgYmVzdCBwcmVkaWN0b3Igb2YgZWFjaCBmaXRuZXNzIGNvbXBvbmVudCBpcyBkaWZmZXJlbnQgYmV0d2VlbiB0aGUgdHdvIHNwZWNpZXMuCgoqSXMgdGhlcmUgYW55dGhpbmcgeW91J2QgYmUgY2F1dGlvdXMgYWJvdXQgaW4gbWFraW5nIHRoaXMgY29tcGFyaXNvbj8qCgo+IERpZmZlcmVudCBkZXRlY3Rpb24gYWJpbGl0aWVzIGZvciB0aGUgdHdvIHNwZWNpZXMsIGJlY2F1c2Ugb25lIGlzIGNvbW1vbmVyIHRoYW4gdGhlIG90aGVyLCBhbmQgZWFzaWVyIHRvIGdldCBhIGxhcmdlIHNhbXBsZS4KCj4gV2F0Y2ggb3V0IGZvciBzdHVkZW50cyB1c2luZyBzdGF0aXN0aWNhbCBzaWduZmljYW5jZSBvbmx5IHRvIGd1aWRlIGNvbmNsdXNpb25zCgo+IERpZmZlcmVudCBzZW5zaXRpdmllcyBwcm9iYWJseSBtZWFuIHRoYXQgdHdvIG9mIHRoZSAzIGVmZmVjdHMgZm9yIEEuIHZpcmlkaWZsb3JhIHdvdWxkbid0IGhhdmUgYmVlbiBkZXRlY3RlZCB3aXRoIHRoZSBBLiBzeXJpYWNhIHNhbXBsZSBzaXplCgo+IFN0YW5kYXJkaXplZCBjb2VmZmljaWVudHMgc3VnZ2VzdCB3ZWFrZXIgb3ZlcmFsbCBlZmZlY3RzIGluIEEuIHZpcmlkaWZsb3JhCgojIyMgQwoKUmVjYWxsIHRoZSBzZW5naSBleGFtcGxlIGZyb20gQ2hhcHRlciA1IChvciBnbyBiYWNrIGFuZCBsb29rIGF0IGl0IDstKSkuIFRoZSByZXNlYXJjaCBxdWVzdGlvbnMgYXJlIGFib3V0IGZhbWlseSBkaWZmZXJlbmNlcy4gQ291bGQgYWxzbyBsb29rIGF0IHRoaXMgcmVsYXRpb25zaGlwIGJldHdlZW4gc2VuZ2lzIGFuZCB0aGUgcmVzdC4gVGhlcmUgYXJlIDIgb3IgMyBncm91cHMgb2YgaW5zZWN0aXZvcmVzLCBzZW5naXMgYW5kIGNsb3NlbHkgKGFmcm90aGVyaWFuKSBhbmQgbW9yZSBkaXN0YW50bHkgKGxhdXJhc2lhdGhlcmlhbikgc3BlY2llcywgYW5kIHRoZSByZXNlYXJjaCBxdWVzdGlvbiBpcyBhYm91dCB3aGVyZSBzZW5naXMgZml0LiBXZSBjYW4gZnJhbWUgdGhpcyBhcyAyIG9yIDMgcXVlc3Rpb25zLgoKMS4gIERvZXMgdGhlIG5ldyBzcGVjaWVzICgqdWR6dWd3ZW5zaXMqKSBmaXQgd2l0aGluIHRoZSBwYXR0ZXJuIG9mIG90aGVyIHNlbmdpPwoKMi4gIEFyZSBzZW5naSBkaWZmZXJlbnQgZnJvbSB0aGUgb3RoZXIgc21hbGwgaW5zZWN0aXZvcmVzIGluIHRoZWlyIGJyYWluIHNpemU/CgogICAgMS4gIHNlbmdpIHZzIGFsbCBvdGhlcnMsIG9yCgogICAgMi4gIHNlbmdpIHZzIGNsb3NlbHktcmVsYXRlZCB2cyBkaXN0YW50bHkgcmVsYXRlZCBpbnNlY3Rpdm9yZXMKCkdldCBzdGFydGVkIGJ5IGxvYWRpbmcgdGhlIGthdWZtYW4gZGF0YS4gSW4gQ2hhcHRlciA1LCB5b3Ugc2hvdWxkIGhhdmUgZGVjaWRlZCB0aGF0IGxvZy10cmFuc2Zvcm1pbmcgYm90aCB2YXJpYWJsZXMgd2FzIHNlbnNpYmxlLCBzbyBsZXRzIGFsc28gc3RhcnQgYnkgZGVmaW5pbmcgbmV3IHZhcmlhYmxlcyBsb2dicmFpbiBhbmQgbG9nIGJvZHkuIFRoYXQgd2lsbCBtYWtlIHRoZSBjb2RpbmcgdGlkaWVyLCB3aXRob3V0IGhhdmluZyB0byBsb2cgdGhpbmdzIHJlcGVhdGVkbHkuCgpgYGB7cn0KZGYgPC0gcmVhZC5jc3YoImRhdGEva2F1Zm1hbi5jc3YiKQpkZiRsb2dicmFpbiA8LSBsb2coZGYkYnJhaW5tYXNzKQpkZiRsb2dib2R5IDwtIGxvZyhkZiRib2R5bWFzcykKYGBgCgpGb3IgdGhlIGZpcnN0IHF1ZXN0aW9uLCBjYXN0IHlvdXIgbWluZCBiYWNrIHRvIENoYXB0ZXIgNi4gSG93IHdvdWxkIHlvdSBkZWNpZGUgd2hldGhlciB0aGUgbmV3IHNwZWNpZXMgaXMgdW51c3VhbD8KCj4gVXNlIHRoZSBleGlzdGluZyBzcGVjaWVzIHRvIGNhbGN1bGF0ZSBhIGxpbmVhciByZWdyZXNzaW9uLCB0aGVuIGNhbGN1bGF0ZSB0aGUgcHJlZGljdGVkIHZhbHVlIGZvciBhIHNwZWNpZXMgb2YgdGhlIGJvZHkgbWFzcyBvZiAqUi4gdWR6dWd3ZW5zaXMqLgoKYGBge3J9CnNlbmdpIDwtIGZpbHRlcihkZiwgZmFtaWx5ID09ICJNYWNyb3NjZWxpZGlkYWUiKQpzZW5naQpzZW5naS5sbSA8LSBsbShkYXRhPWZpbHRlcihzZW5naSwgc3BlY2llcyAhPSAidWR6dWd3ZW5zaXMiKSwgbG9nYnJhaW4gfiBsb2dib2R5KQpnbGFuY2Uoc2VuZ2kubG0pCnRpZHkoc2VuZ2kubG0pCgojIE5vdyBwcmVkaWN0IHZhbHVlcyBmb3IgKlIuIHVkenUuLioKCnByZWRpY3Qoc2VuZ2kubG0sIGRhdGEuZnJhbWUobG9nYm9keSA9IGMoNi41NjUpKSwgaW50ZXJ2YWw9InByZWRpY3Rpb24iLCBzZT1UKQoKYGBgCgo+IFByZWRpY3RlZCBsb2cgYnJhaW4gbWFzcyBpcyA4LjkxLCBhbmQgdGhlIHByZWRpY3Rpb24gaW50ZXJ2YWwgaXMgOC41MCAtIDkuMzEsIHNvIHRoaXMgc3BlY2llcyBpcyBwcmV0dHkgbXVjaCBzbWFjayBvbiB0aGUgbGluZS4KCiMjIyMgTm93IGxldHMgY29tcGFyZSBzZW5naXMgdG8gdGhlIG90aGVyIGluc2VjdGl2b3Jlcy4gVXNlIHRocmVlIGdyb3VwcyBmb3IgY29tcGFyaXNvbiAoc2VuZ2ksIEFmcm90aGVyaWFuIGFuZCBMYXVyYXNpYXRoZXJpYW4pLiBUaGVzZSBncm91cHMgYXJlIGRlZmluZWQgaW4gdGhlIHZhcmlhYmxlICpyZWxhdGlvbioKClwqXCogWW91IGNvdWxkIG1ha2UgdGhpcyBjb21wYXJpc29uIGluIHR3byB3YXlzOgoKLSAgIGZpdCBhIGxpbmVhciBtb2RlbCBpbmNsdWRpbmcgdGhlIGdyb3VwcyBhbmQgbG9nIGJvZHkgbWFzcywgaS5lLiBhbiBhbmFseXNpcyBvZiBjb3ZhcmlhbmNlCi0gICBsb29rIGF0IHRoZSBwYXR0ZXJucyBpbiB0aGUgcmVzaWR1YWxzIGZvciB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gbG9nLWJyYWluIGFuZCBsb2ctYm9keQoKXCpcKkJlZm9yZSB5b3Ugc3RhcnQsIGFyZSB0aGVyZSBhbnkgdGhpbmdzIHRvIGNoZWNrIGluIHRoZSBvcmlnaW5hbCBkYXRhLCBsaW5rZWQgdG8gdGhlIGFzc3VtcHRpb25zIG9mIHRoZSBsaW5lYXIgbW9kZWwgeW91J2xsIGZpdD8KCj4gVGhlIG90aGVyIGltcG9ydGFudCB0aGluZyB0byBub3RlIGlzIHRoYXQgaWYgd2UncmUgbG9va2luZyB0byBjb21wYXJlIHNlbmdpcyBhbmQgb3RoZXIgZ3JvdXBzIGZvcm1hbGx5LCB3ZSBuZWVkIHRvIGJlIGNhcmVmdWwgYWJvdXQgdGhlIHJhbmdlcyBvZiBib2R5IHNpemVzLiBTZW5naSBzcGVjaWVzIHN0YXJ0IGFyb3VuZCB0aGUgbWlkZGxlIG9mIHRoZSByYW5nZSwgZnJvbSA0NWcgdXAuIFdlJ2QgcHJvYmFibHkgcmVzdHJpY3Qgb3VyIGNvbXBhcmlzb24gdG8gc3BlY2llcyBhcm91bmQgdGhpcyBzaXplIC0gZXhjbHVkZSB0aG9zZSB3aXRoIGJvZHkgbWFzcyBsZXNzIHRoYW4gYSB0aHJlc2hvbGQuIFRocmVzaG9sZCB3b3VsZCBiZSBhcmJpdHJhcnksIGJ1dCBtb3N0IHBlb3BsZSB3b3VsZCBjaG9vc2UgNDAgb3IgNDVnCgpgYGB7cn0KZ2dwbG90KGRhdGE9ZGYsIGFlcyh4PWxvZyhib2R5bWFzcyksIHk9bG9nKGJyYWlubWFzcyksIGNvbG91ciA9IHJlbGF0aW9uLCBzaGFwZSA9IHJlbGF0aW9uKSkrCiAgZ2VvbV9wb2ludCgpKyB0aGVtZV9saWdodCgpICsgc2NhbGVfY29sb3JfdWNoaWNhZ28oKQpgYGAKCiMjIyMjIEFuYWx5c2lzIG9mIGNvdmFyaWFuY2UKCioqT3V0bGluZSB0aGUgc3RlcHMgeW91J2xsIHRha2UqKgoKPiAxLiAgRml0IGNvbXBsZXggbW9kZWwgYWxsb3dpbmcgc2xvcGVzIHRvIHZhcnkgYW1vbmcgZ3JvdXBzCj4gMi4gIEFzc2VzcyB3aGV0aGVyIHRoZSBncm91cHNcKmxvZ2JvZHkgdGVybSBzaG91bGQgcmVtYWluIGluIHRoZSBtb2RlbAo+IDMuICBJZiByZWxhdGlvbnNoaXBzIGNhbiBiZSB0cmVhdGVkIGFzIHBhcmFsbGVsLCBydW4gc2ltcGxlciBtb2RlbCB3aXRoIGdyb3VwcyBhbmQgbG9nYm9keQo+IDQuICBFeGFtaW5lIHJlc3VsdHMgYW5kIGRlY2lkZSB3aGV0aGVyIHRvIGRvIGFueSBjb21wYXJpc29ucyBhbW9uZyBncm91cHMKCmBgYHtyfQpkZiRyZWxhdGlvbjwtZmFjdG9yKGRmJHJlbGF0aW9uKQpkZiRyZWxhdGlvbjI8LWZhY3RvcihkZiRyZWxhdGlvbjIpCmNvbnRyYXN0cyA9IGxpc3QgKHJlbGF0aW9uID0gY29udHIuc3VtKQojIENyZWF0ZSBzdWJzZXQgb2YgZmlsZS4gV2UgY291bGQga2VlcCBydW5uaW5nIHRoZSBmaWx0ZXIgZWFjaCB0aW1lIHdlIHJ1biB0aGUgbW9kZWwsIGJ1dCBkb2luZyBpdCBvbmNlIGlzIHRpZGllci4KZGYyIDwtIGZpbHRlcihkZiwgYm9keW1hc3MgPiA0MCkKbG0yPC1hb3YobG9nYnJhaW5+bG9nYm9keSArIHJlbGF0aW9uICsgbG9nYm9keSpyZWxhdGlvbiwgZGF0YSA9IGRmMikKZ2xhbmNlKGxtMikKdGlkeShsbTIpCkFub3ZhKGxtMiwgdHlwZT0nSUlJJykKYGBgCgo+IGxvZ2JvZHlcKnJlbGF0aW9uIHRlcm0gY29udHJpYnV0aW5nIGxpdHRsZSB0byBtb2RlbCBmaXQsIHNvIHJ1biBzaW1wbGVyIG1vZGVsCgpgYGB7cn0KbG0zPC1hb3YobG9nYnJhaW5+bG9nYm9keSArIHJlbGF0aW9uLCBkYXRhID0gZGYyKQpnbGFuY2UobG0zKQp0aWR5KGxtMykKQW5vdmEobG0zLCB0eXBlPSdJSUknKQoKI0dldCBhZGp1c3RlZCBtZWFucwpsaWJyYXJ5KGVmZmVjdHMpCmFkam1lYW5zIDwtIGVmZmVjdCgicmVsYXRpb24iLCBsbTMsIHNlPVRSVUUpCnN1bW1hcnkoYWRqbWVhbnMpCmdncGxvdChkYXRhPWRmMiwgYWVzKHg9bG9nKGJvZHltYXNzKSwgeT1sb2coYnJhaW5tYXNzKSwgZ3JvdXA9IHJlbGF0aW9uLCBjb2xvdXIgPSByZWxhdGlvbiwgc2hhcGUgPSByZWxhdGlvbikpKwogIGdlb21fcG9pbnQoKSsgZ2VvbV9zbW9vdGgobWV0aG9kPWxtKSArIHRoZW1lX2xpZ2h0KCkgKyBzY2FsZV9jb2xvcl91Y2hpY2FnbygpCmBgYAoKPiBObyBuZWVkIHRvIHByb2NlZWQgZnVydGhlci4gV2UgY29uY2x1ZGUgdGhhdCBzbG9wZXMgZGlmZmVyIGJldHdlZW4gdGhlIGdyb3VwcywgYW5kIHNlbmdpIGFyZSBjbGVhcmx5IGFib3ZlIHRoZSBvdGhlciB0d28uIFRoZSBvdGhlciB0d28gZ3JvdXBzIGFyZSBjbG9zZSwgd2l0aCBhYnV0dGluZyBjb25maWRlbmNlIGludGVydmFscyBhcm91bmQgdGhlIGFkanVzdGVkIG1lYW5zLgoKIyMjIyMgVXNlIHJlc2lkdWFscyBmcm9tIGEgcmVncmVzc2lvbiBvZiBhbGwgZGF0YSBhbmQgY29tcGFyZSByZXNpZHVhbHMgYmV0d2VlbiBncm91cHMKCmBgYHtyfQpsbTQgPC0gbG0obG9nKGJyYWlubWFzcykgfiBsb2coYm9keW1hc3MpLCBkYXRhPWRmMikKZGYzIDwtIGNiaW5kKGRmMiwgbG00JHJlc2lkdWFscykKaGVhZChkZjMsMTApCmJveHBsb3QobG00JHJlc2lkdWFscyB+IHJlbGF0aW9uLCBkYXRhID0gZGYzKSAKbG01IDwtIGFvdiAobG00JHJlc2lkdWFscyB+IHJlbGF0aW9uLCBkYXRhID0gZGYzKQpzdW1tYXJ5KGxtNSkKYGBgCgo+IFBhdHRlcm4gYWxzbyBjbGVhciBoZXJlLCBqdXN0IGZyb20gYm94IHBsb3RzCgojIyMgRAoKV2UnbGwgcmV0dXJuIHRvIHRoZSBlbGVwaGFudCBzZWFsIGV4YW1wbGUgaW4gdGhlIHN0dWR5IGJ5IExhQm9ldWYgZXQgYWwuLCBhbmQgc2VlIHdoZXRoZXIgYm9keSB3ZWlnaHQgcGxheXMgYW55IHJvbGUgaW4gZm9yYWdpbmcuIEluIENoYXB0ZXIgNSwgeW91IHNob3VsZCBoYXZlIG5vdGljZWQgdGhhdCB3aGlsZSB0aGUgZm9jdXMgb2YgdGhlIGluaXRpYWwgYW5hbHlzaXMgd2FzIG9uIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBkaXN0YW5jZSB0cmF2ZWxsZWQgYW5kIHRpbWUgc3BlbmQgb24gdGhlIGZvcmFnaW5nIGdyb3VuZHMsIHRoZSBhdXRob3JzIHJlY29yZGVkIHdlaWdodCBvbiBkZXBhcnR1cmUgZm9yIGVhY2ggYW5pbWFsLiBZb3VyIGV4cGxvcmF0b3J5IGRhdGEgYW5hbHlzaXMgc2hvdWxkIGhhdmUgc2hvd24gYSByZWxhdGlvbnNoaXAgYmV0d2VlbiBib2R5IHdlaWdodCBhbmQgdGhlIG9yaWdpbmFsIHByZWRpY3RvciBhbmQgcmVzcG9uc2UgdmFyaWFibGVzLiBOb3cgdHJ5IGFuZCBtYWtlIHNvbWUgc2Vuc2Ugb2Ygd2hhdCdzIGdvaW5nIG9uIGhlcmUuCgpgYGB7cn0KI0dldCB0aGUgZGF0YSBmaWxlIGJhY2sKZGYgPC0gcmVhZC5jc3YoImRhdGEvbGVib2V1Zi5jc3YiKQpoZWFkKGRmLDEwKQpgYGAKCioqVGhpbmsgYWJvdXQgaG93IGJvZHkgbWFzcyBtaWdodCBpbmZsdWVuY2UgZGlzdGFuY2UgdHJhdmVsbGVkIGFuZCBob3cgaXQgbWlnaHQgY29udHJpYnV0ZSB0byB0aW1lIG9uIGZvcmFnaW5nIGFyZWFzKioKCj5NYXNzIGNvdWxkIGVhc2lseSBiZSBsaW5rZWQgdG8gc3dpbW1pbmcgc3BlZWQsIGFsbG93aW5nIGxhcmdlciBhbmltYWxzIHRvIGZvcmFnZSBmb3IgbG9uZ2VyLiBXZSBjb3VsZCBldmVuIGdldCBtb3JlIGNvbXBsaWNhdGVkIGFuZCBzcGVjdWxhdGUgd2hldGhlciBsYXJnZXIgbWFsZXMgbWF5IHNwZW5kIG1vcmUgdGltZSBtYWludGFpbmluZyBkb21pbmFuY2Ugb24gdGhlIHNob3JlLCBzbyBtaWdodCBmb3JhZ2UgbGVzcy4gSW4gZWl0aGVyIGNhc2UsIHRoZSB0d28gdmFyaWFibGVzIGNvdWxkIGJlIGxpbmtlZC4KCj5Cb2R5IG1hc3MgbWlnaHQgYWxzbyByZWZsZWN0IG92ZXJhbGwgY29uZGl0aW9uLCBhbmQsIGZvciBleGFtcGxlLCBtYWxlcyBpbiBwb29yZXIgY29uZGlkdGlvbiBtaWdodCBuZWVkIHRvIHNwZW5kIGxvbmdlciBmZWVkaW5nLgoKKipIb3cgd2lsbCB5b3UgYXNzZXNzIHdoZXRoZXIgaW5jbHVkaW5nIGJvZHkgd2VpZ2h0IGFzIGEgc2Vjb25kIHByZWRpY3RvciBoZWxwcyB1cyB1bmRlcnN0YW5kIGZlZWRpbmcgdGltZSBiZXR0ZXI/KioKCi0gICBXcml0ZSBvdXQgdGhlIGxpbmVhciBtb2RlbCB5b3UnZCBhcHBseQoKPiBUaW1lIG9uIGZvcmFnaW5nID0gKs6yfjB+KiArICrOsn4xfiogRGlzdGFuY2UgdHJhdmVsbGVkICsgKs6yfjJ+KiBCb2R5IHdlaWdodAoKLSAgIFdoYXQgY2hlY2tzIGRvIHlvdSBuZWVkIHdoZW4gZml0dGluZyB0aGUgbW9kZWw/Cgo+IE5lZWQgdG8gY2hlY2sgY29sbGluZWFyaXR5IC0gdXNlIFZJRgoKPiBDaGVjayByZXNpZHVhbHMKCj4gQ2hlY2sgbGluZWFyaXR5CgpgYGB7cn0KY29yKGRmWyxjKCdkaXN0YW5jZScsJ2RlcGFydHd0JyldKQpsYWJvZXVmMS5sbSA8LSBsbSAoRkZBZHVyYXRpb25+IGRpc3RhbmNlICsgZGVwYXJ0d3QsIGRhdGEgPSBkZikKdmlmKGxhYm9ldWYxLmxtKQpwbG90KGxhYm9ldWYxLmxtKQpgYGAKCj4gVklGIHZhbHVlIG5vdCBzbWFsbCwgYnV0IGJlbG93IHRoZSBjb21tb24gdGhyZXNob2xkIG9mIDUKCj4gTm90aGluZyBkcmFtYXRpYyBpbiByZXNpZHVhbHMsIHRob3VnaCBwb2ludHMgMTMsIDE0LCBhbmQgMTYgaGF2ZSBsYXJnZSByZXNpZHVhbHMKCmBgYHtyfQphdWdtZW50KGxhYm9ldWYxLmxtKQpgYGAKCgoqKkZpdCB0aGUgYXBwcm9wcmlhdGUgbW9kZWwgdG8gdGhlIGRhdGEsIGludGVycHJldCB0aGUgcmVzdWx0cywgYW5kIGV4cGxhaW4gd2hldGhlciBib2R5IHdlaWdodCBoZWxwcyB1cy4qKgoKPkdldCB0aGUgZXF1YXRpb24KCmBgYHtyIGVjaG89RkFMU0UsIHJlc3VsdHM9J2FzaXMnfQplcXVhdGlvbWF0aWM6OmV4dHJhY3RfZXEobGFib2V1ZjEubG0pCmBgYAoKCmBgYHtyfQpnbGFuY2UobGFib2V1ZjEubG0pCnRpZHkobGFib2V1ZjEubG0pCmBgYAoKPiBHZXQgc3RhbmRhcmlzZWQgcmVncmVzc2lvbiBjb2VmZmljaWVudHMKCj4gVXNlIGxtLmJldGEgZnVuY3Rpb24gZnJvbSBsbS5iZXRhIHBhY2thZ2UKCmBgYHtyIH0KbG0uYmV0YS5sYWJvZXVmIDwtIGxtLmJldGEobGFib2V1ZjEubG0pCnRpZHkobG0uYmV0YS5sYWJvZXVmLCBjb25mLmludCA9IFRSVUUpCmdsYW5jZShsbS5iZXRhLmxhYm9ldWYpCmBgYAoKPiBTaG93IHRoZSBwYXJ0aWFsIHJlZ3Jlc3Npb24gKGFkZGVkLXZhcmlhYmxlKSBwbG90cwoKYGBge3IgfQphdlBsb3RzKGxhYm9ldWYxLmxtLCBhc2s9RikKYGBgCgo+IENvbmNsdXNpb24gaXMgdGhhdCB0aGVyZSdzIGEgc3Ryb25nIHN0YXRpc3RpY2FsIHJlbGF0aW9uc2hpcC4gVGhlIG1vZGVsIGV4cGxhaW5zIGFyb3VuZCA2MCUgb2YgdmFyaWF0aW9uLCBhbmQgdGhhdCBlZmZlY3QgaXMgbGFyZ2VseSBhc3NvY2lhdGVkIHdpdGggZGlzdGFuY2UgdHJhdmVsbGVkLiBUaW1lIG9uIGZvcmFnaW5nIGdyb3VuZHMgZmFsbHMgc2hhcnBseSB3aXRoIGRpc3RhbmNlLgoKPiBEZXBhcnR1cmUgcmF0ZSBwbGF5cyBsaXR0bGUgcm9sZS4KCioqSXMgdGhlcmUgYW55dGhpbmcgZWxzZSB5b3UgbWlnaHQgbG9vayBhdD8qKgoKPiBUaGUgcmVsYXRpb25zaGlwIG9mIGRpc3RhbmNlIHRvIGJvdGggcmVzcG9uc2UgYW5kIHRoZSBvdGhlciBwcmVkaWN0b3IgbWlnaHQgbWFrZSBpdCB3b3J0aCBsb29raW5nIGZvciBhIG5vbi1hZGRpdGl2ZSBtb2RlbAoKYGBge3J9CmxhYm9ldWYyLmxtIDwtIGxtIChGRkFkdXJhdGlvbn4gZGlzdGFuY2UgKyBkZXBhcnR3dCArIGRpc3RhbmNlKmRlcGFydHd0LCBkYXRhID0gZGYpCnZpZihsYWJvZXVmMi5sbSkKIyBIaWdoIHZpZiB2YWx1ZXMsIGFzIGV4cGVjdGVkLCBzbyBjZW50cmUgcHJlZGljdG9ycyBhbmQgcnVuIGFnYWluCmRmJGNkaXN0YW5jZSA8LSBzY2FsZShkZiRkaXN0YW5jZSwgY2VudGVyPVRSVUUsIHNjYWxlPUZBTFNFKQpkZiRjZGVwYXJ0d3QgPC0gc2NhbGUoZGYkZGVwYXJ0d3QsIGNlbnRlcj1UUlVFLCBzY2FsZT1GQUxTRSkKbGFib2V1ZjMubG0gPC0gbG0gKEZGQWR1cmF0aW9ufiBjZGlzdGFuY2UgKyBjZGVwYXJ0d3QgKyBjZGlzdGFuY2UqY2RlcGFydHd0LCBkYXRhID0gZGYpCnZpZihsYWJvZXVmMy5sbSkKbG0uYmV0YS5sYWJvZXVmMyA8LSBsbS5iZXRhKGxhYm9ldWYzLmxtKQp0aWR5KGxtLmJldGEubGFib2V1ZjMsIGNvbmYuaW50ID0gVFJVRSkKZ2xhbmNlKGxtLmJldGEubGFib2V1ZjMpCgpgYGAKCj5BY3R1YWxseSBhIHdvcnNlIG1vZGVsOyBtb3JlIGlzc3VlcyB3aXRoIFZJRiwgYWRqdXN0ZWQgci1zcSBsb3dlciwgYW5kIGNvbWJpbmVkIHRlcm0gY29udHJpYnV0ZXMgbm90aGluZw==